package br.com.ps.bombertournament.game;

import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Map;

import br.com.ps.bombertournament.game.scene.BaseScene;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.graphics.OrthographicCamera;

import flexjson.JSONDeserializer;

/**
 * Responsable to load a scene based on a configurations files
 * 
 * @author Rennan Stefan Boni - rennanboni@gmail.com
 */
public abstract class MappedScene extends BaseScene {

	private static final String COLLISION_PROPERTY = "collision";
	private static final String ENGINE_PROPERTY = "engine";
	private static final String ID_PROPERTY = "id";
	private static final String LOOP_PROPERTY = "loop";
	private static final String FILE_PROPERTY = "file";
	private static final String MUSIC_PROPERTY = "music";
	private static final String VOLUME_PROPERTY = "volume";
	private static final String OBJECTS_PROPERTY = "objects";
	private static final String CLAZZ_PROPERTY = "clazz";
	private static final String PRIORITY_PROPERTY = "priority";
	private static final String LAYERS_PROPERTY = "layers";
	private static final String HEIGHT_PROPERTY = "height";
	private static final String WIDTH_PROPERTY = "width";
	private static final String Y_PROPERTY = "y";
	private static final String X_PROPERTY = "x";
	private static final String CAMERA_PROPERTY = "camera";


	public void create() {
		super.create();
		
		loadScene();
	}
	
	
	/**
	 * Load the mapped scene based on configuration file
	 */
	private void loadScene() {
		String json = Gdx.files.internal(getMapPath()).readString();
		
		WrapperMap configurations = new WrapperMap(new JSONDeserializer<Map<String, Object>>().deserialize(json));
		
		{ // Loading the camera's configurations
			if (configurations.containsKey(CAMERA_PROPERTY)) {
				WrapperMap cameraConfigurations = configurations.getMap(CAMERA_PROPERTY);
				
				if (cameraConfigurations.containsKey(X_PROPERTY) 
						&& cameraConfigurations.containsKey(Y_PROPERTY)
						&& cameraConfigurations.containsKey(WIDTH_PROPERTY)
						&& cameraConfigurations.containsKey(HEIGHT_PROPERTY)) {
					OrthographicCamera camera = new OrthographicCamera();
					
					camera.position.x = cameraConfigurations.getInteger(X_PROPERTY);
					camera.position.y = cameraConfigurations.getInteger(Y_PROPERTY);
					camera.viewportWidth = cameraConfigurations.getInteger(WIDTH_PROPERTY);
					camera.viewportHeight = cameraConfigurations.getInteger(HEIGHT_PROPERTY);
					
					super.setCamera(camera);
				}
			}
		}
		
		{ // Loading layers
			if (configurations.containsKey(LAYERS_PROPERTY)) {
				List<WrapperMap> layersConfigurations = configurations.getList(LAYERS_PROPERTY);
				
				for (WrapperMap layerConfiguration : layersConfigurations) {
					Integer priority = layerConfiguration.getInteger(PRIORITY_PROPERTY);
					
					super.getGame().addLayer(priority, createLayer(layerConfiguration));
				}
			}
		}
		
		{ // Loading the music's scene
			if (configurations.containsKey(MUSIC_PROPERTY)) {
				WrapperMap musicConfigurations = configurations.getMap(MUSIC_PROPERTY);
				
				if (musicConfigurations.containsKey(FILE_PROPERTY)) {
					Music music = Gdx.audio.newMusic(Gdx.files.internal(musicConfigurations.getString(FILE_PROPERTY)));
					
					music.setLooping(musicConfigurations.getBoolean(LOOP_PROPERTY, false));
					
					if (musicConfigurations.containsKey(VOLUME_PROPERTY)) {
						music.setVolume(musicConfigurations.getFloat(VOLUME_PROPERTY));
					}
					
					super.getGame().setMusic(music);
				}
			}
		}
		
		{ // Loading the engine collision
			if (configurations.containsKey(ENGINE_PROPERTY)) {
				WrapperMap engineConfigurations = configurations.getMap(ENGINE_PROPERTY);
				if (engineConfigurations.containsKey(COLLISION_PROPERTY)) {
					
					try {
						Class<?> clazz = Class.forName(engineConfigurations.getString(COLLISION_PROPERTY));
						
						super.getGame().setCollisionEngine((CollisionEngine) newInstance(clazz, engineConfigurations));
					} catch (ClassNotFoundException e) {
						e.printStackTrace();
					}
					
				}
			}
		}
	}
	
	/**
	 * Create a new Layer based on configurations properties
	 * 
	 * @param layerConfigurations Configuration properties of the layer
	 * @return Return a new layer
	 */
	private Layer createLayer(WrapperMap layerConfigurations) {
		Layer result = null;
		
		if (layerConfigurations.containsKey(OBJECTS_PROPERTY)) {
			List<WrapperMap> objects = layerConfigurations.getList(OBJECTS_PROPERTY);
			
			if (objects != null) {
				result = new Layer(super.getGame(), objects.size(), layerConfigurations.getInteger(PRIORITY_PROPERTY));
				
				for (WrapperMap object : objects) {
					result.add(createGameObject(object));
				}
			}
		}
		
		result.addPending();
		
		return result;
	}


	/**
	 * Create a new game object based on properties configurations
	 * 
	 * @param objectConfigurations Properties configurations of the game object
	 * @return Return a new game object
	 */
	private BaseGameObject createGameObject(WrapperMap objectConfigurations) {
		MappedGameObject result = null;
		
		if (objectConfigurations.containsKey(CLAZZ_PROPERTY)) {
			String clazzStr = objectConfigurations.getString(CLAZZ_PROPERTY);
			
			try {
				Class<?> clazz = Class.forName(clazzStr);
				
				result = (MappedGameObject) newInstance(clazz, objectConfigurations);
				
				{ // Loading/Applying game object properties
					if (result != null) {
						result.setConfigurations(objectConfigurations);
						
						result.setPosition(objectConfigurations.getFloat(X_PROPERTY, 0), objectConfigurations.getFloat(Y_PROPERTY, 0));
						
						result.setId(objectConfigurations.getString(ID_PROPERTY));
						
						result.loadAnimations();
						
						if (objectConfigurations.containsKey("scale.x") && objectConfigurations.containsKey("scale.y")) {
							result.setScale(objectConfigurations.getFloat("scale.x"), objectConfigurations.getFloat("scale.y"));
						}
					}
				}
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
		
		return result;
	}
	
	/**
	 * Loading the class object that represents the game object business logic
	 * 
	 * @param clazz The class name to loaded 
	 * @param objectConfigurations Properties configurations to be loaded and apply
	 * @return
	 */
	private Object newInstance(Class<?> clazz, WrapperMap objectConfigurations) {
		Object result = null;
		
		try {
			Constructor<?> contructor = clazz.getConstructor(WrapperMap.class);
			
			result = contructor.newInstance(objectConfigurations);
		} catch (Exception e) {
			try {
				result = clazz.newInstance();
				
				if (result instanceof MappedGameObject) {
					((MappedGameObject) result).setConfigurations(objectConfigurations);
				}
			} catch (InstantiationException e1) {
				e1.printStackTrace();
			} catch (IllegalAccessException e1) {
				e1.printStackTrace();
			}
		}
		
		return result;
	}

	/**
	 * Inform the path of configurations file that represents the base configurations of the game object 
	 * 
	 * @return Return the path of file to be interpreted
	 */
	public abstract String getMapPath();

}
